/* 
 * libsaxychord
 *
 * Choi Jonghwan<saxboy@saxboy.pe.kr>
 * 2005/02/19
 *
 * - Written in KLDP CodeFest :-)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "chord.h"

#include <assert.h>

CHORD chord_New(void)
{
	CHORD c=NULL; 

	if ( (c = (CHORD)malloc(sizeof(_CHORD)))!=NULL) 
		memset(c, 0, sizeof(_CHORD)); 

	return c; 
}

CHORD chord_NewFromString(char *chordstr)
{
	CHORD c=NULL; 

	if (chordstr==NULL) goto error; 
	if (chordstr[0]=='\0') goto error; 

	if ( (c=chord_New())==NULL) goto error; 
	if ( chord_ImportString(c, chordstr) < 0 ) goto error; 

	return c; 

error:
	if ( c!=NULL) chord_Delete(c); 
	return NULL; 
}

int chord_Delete(CHORD c)
{
	if ( c==NULL) return -1; 
	free(c); 
	return 1; 
}

static int _chord_tk2note(int tknote, int flat_sharp)
{
	int note=0; 
	//note = tknote; 
	switch (tknote)
	{
		case 0: note=NOTE_C; break; /* C */
		case 1: note=NOTE_D; break; /* D */
		case 2: note=NOTE_E; break; /* E */
		case 3: note=NOTE_F; break; /* F */
		case 4: note=NOTE_G; break; /* G */
		case 5: note=NOTE_A; break; /* A */
		case 6: note=NOTE_B; break; /* B */
		default: note=-1; 
	}
	note += ( flat_sharp>0? 1 : ( flat_sharp<0 ? -1 : 0 )); 
	return note; 
}
static int _chord_tk2var(int flat_sharp, int num)
{
	int var=0; 

	if ( flat_sharp < 0 ) 
		var = CHORD_VAR_FLAT; 
	else if ( flat_sharp > 0 )
		var = CHORD_VAR_SHARP; 
	else 
		var = 0; 

	var += num; 
	return var; 
}
static int _chord_add_var(CHORD c, int var)
{
	if ( c->chord_var_count >= MAX_CHORD_VARS )
		return -1; 
	c->chord_var[c->chord_var_count] = var; 
	c->chord_var_count++; 
	return 1; 
}

int chord_ImportString(CHORD c, char *chordstr)
{
	int token=0; 
	int ret=0; 
	int chord_GenerateNotes(CHORD c); 

	if (c==NULL) return -1; 
	if (chordstr==NULL) return -1; 

	if ( (token = chord_tok(chordstr))< 0 )
		return -1; 

	do {
		if ( token==0 )
			break;
		switch (token ) {
			case TK_NOTE:
				(c->basenote).abs_note = _chord_tk2note(chord_arg, chord_flatsharp);
				
				break;
			case TK_CHORD:
				c->chord_def = chord_category; 
				c->chord_num = chord_arg; 

				break;
			case TK_VARIATION:
				ret = _chord_add_var (
					c, 
					_chord_tk2var( chord_flatsharp, chord_arg)); 
				if ( ret < 0 )
					return -1; 

				break;
			default:
				abort(); 
		}
	} while ( (token=chord_tok(NULL) )!= -1); 


	/* postprocess minor lexer fault. 
	 * it does not process 'C' as major triad
	 *
	 * FIXME: any resonable way to handle it?
	 */
	if ( c->chord_def == CHORD_NONE)
		c->chord_def = CHORD_MAJOR; 

	/* generate chordtones */
	chord_GenerateNotes(c); 


	return 1; 
}

const NOTE chord_BaseNote(CHORD c)
{
	if ( c==NULL) return NULL; 
	//return (const NOTE)&(c->notes[0]); 
	return (const NOTE)(c->root); 
}

const NOTE chord_ThirdNote(CHORD c)
{
	if ( c==NULL) return NULL; 
	//return (const NOTE)&(c->notes[1]); 
	return (const NOTE)(c->third); 
} 

const NOTE chord_FifthNote(CHORD c)
{
	if ( c==NULL) return NULL; 
	//return (const NOTE)&(c->notes[2]); 
	return (const NOTE)(c->fifth); 
}

const NOTE chord_SeventhNote(CHORD c)
{
	if ( c==NULL) return NULL; 
	//return (const NOTE)&(c->notes[3]); 
	return (const NOTE)(c->seventh); 
}

int chord_AddNote(CHORD c, NOTE n)
{
	if ( c==NULL) return -1; 
	if ( n==NULL) return -1; 

	if ( c->note_count >= MAX_CHORD_NOTES ) return -1; 
	c->notes[ c->note_count] = n; 
	c->note_count++; 

	return 1; 
}

/* 
 * Generate notes from the chord definition
 *
 * this is the heart of the whole process. let's go
 *
 * FIXME: can be a lot simplified using arrays... hehe... 
 */
static int _chord_SetMajor(CHORD c)
{
	if (c==NULL) return -1; 

	note_RaiseInterval(c->third, INTERVAL_MAJOR, 3); 	
	note_RaiseInterval(c->fifth, INTERVAL_PERFECT, 5); 	

	switch (c->chord_num)
	{
	case 7:
		note_RaiseInterval(c->seventh, INTERVAL_MAJOR, 7); 	
		break;
	case 6:
		note_RaiseInterval(c->sixth, INTERVAL_MINOR, 6); 	
		break;
	default:
		break;
	}
	return 1; 
}

static int _chord_SetMinor(CHORD c)
{
	if (c==NULL) return -1; 

	note_RaiseInterval(c->third, INTERVAL_MINOR, 3); 	
	note_RaiseInterval(c->fifth, INTERVAL_PERFECT, 5); 	

	switch (c->chord_num)
	{
	case 7:
		note_RaiseInterval(c->seventh, INTERVAL_MINOR, 7); 	
		break;
	case 6:
		note_RaiseInterval(c->sixth, INTERVAL_MINOR, 6); 	
	default:
		break;
	}
	return 1; 
}

static int _chord_SetDominant(CHORD c)
{
	if (c==NULL) return -1; 

	note_RaiseInterval(c->third, INTERVAL_MAJOR, 3); 	
	note_RaiseInterval(c->fifth, INTERVAL_PERFECT, 5); 	

	switch (c->chord_num)
	{
	case 7:
		note_RaiseInterval(c->seventh, INTERVAL_MINOR, 7); 	
		break;
	default:
		break;
	}
	return 1; 
}

static int _chord_SetAugmented(CHORD c)
{
	if (c==NULL) return -1; 

	note_RaiseInterval(c->third, INTERVAL_MAJOR, 3); 	
	note_RaiseInterval(c->fifth, INTERVAL_AUGMENTED, 5); 	

	switch (c->chord_num)
	{
	case 7:
		note_RaiseInterval(c->seventh, INTERVAL_MINOR, 7); 	
		break;
	default:
		break;
	}
	return 1; 
}

static int _chord_SetDiminished(CHORD c)
{
	if (c==NULL) return -1; 

	note_RaiseInterval(c->third, INTERVAL_MINOR, 3); 	
	note_RaiseInterval(c->fifth, INTERVAL_DIMINISHED, 5); 	

	switch (c->chord_num)
	{
	case 7:
		note_RaiseInterval(c->seventh, INTERVAL_MINOR, 6); 	
		break;
	default:
		break;
	}
	return 1; 
}

static int _chord_SetHalfDiminished(CHORD c)
{
	if (c==NULL) return -1; 

	note_RaiseInterval(c->third, INTERVAL_MINOR, 3); 	
	note_RaiseInterval(c->fifth, INTERVAL_DIMINISHED, 5); 	

	switch (c->chord_num)
	{
	case 7:
		note_RaiseInterval(c->seventh, INTERVAL_MINOR, 6); 	
		break;
	default:
		break;
	}
	return 1; 
}

static int _chord_SetSuspended(CHORD c)
{
	NOTE n=NULL; 
	if (c==NULL) return -1; 

	note_RaiseInterval(c->fifth, INTERVAL_PERFECT, 5); 	

	switch (c->chord_num)
	{
	case 4:
		if ( (n = note_Copy( &(c->basenote)))==NULL) return -1; 
		chord_AddNote(c, n); 
		c->fourth = n; 
		note_RaiseInterval(c->fourth, INTERVAL_PERFECT, 4); 	
		break;
	case 2:
		if ( (n = note_Copy( &(c->basenote)))==NULL) return -1; 
		chord_AddNote(c, n); 
		c->fourth = n; 
		note_RaiseInterval(c->second, INTERVAL_PERFECT, 4); 	
		break;
	default:
		break;
	}
	return 1; 
}


/* add as many notes as needed, and fill it with root 
 */
static int _chord_FillNotes(CHORD c)
{
	NOTE n=NULL; 
	int chord_num=0; 

	if (c==NULL) return -1; 

	/* select how many notes should be generated */
	chord_num = c->chord_num; 
	{
		int i=0; 
		for (i=0; i<c->chord_var_count; i++)
		{
			int varnote = c->chord_var[i] % 128; 
			if (varnote > chord_num)
				chord_num = varnote; 
		}
	}

	/* root */
	if ( (n = note_Copy( &(c->basenote)))==NULL) return -1; 
	chord_AddNote(c, n); 
	c->root = n; 

	/* 3rd */
	/* suspended does not have 3rd */
	if ( c->chord_def != CHORD_SUSPENDED)
	{
		if ( (n = note_Copy( &(c->basenote)))==NULL) return -1; 
		chord_AddNote(c, n); 
		c->third = n; 
	}

	/* 5th */
	if ( (n = note_Copy( &(c->basenote)))==NULL) return -1; 
	chord_AddNote(c, n); 
	c->fifth = n; 

	/* triads should not have 7th */
	switch (c->chord_num)
	{
	case 13:
		/* 13th */
		if ( (n = note_Copy( &(c->basenote)))==NULL) return -1; 
		chord_AddNote(c, n); 
		c->thirteenth = n; 
	case 11:
		/* 11th */
		if ( (n = note_Copy( &(c->basenote)))==NULL) return -1; 
		chord_AddNote(c, n); 
		c->eleventh = n; 
	case 9:
		/* 9th */
		if ( (n = note_Copy( &(c->basenote)))==NULL) return -1; 
		chord_AddNote(c, n); 
		c->ninth = n; 
	case 7:
		/* 7th */
		if ( (n = note_Copy( &(c->basenote)))==NULL) return -1; 
		chord_AddNote(c, n); 
		c->seventh = n; 
		break;

	case 6:
		/* 6th */
		if ( (n = note_Copy( &(c->basenote)))==NULL) return -1; 
		chord_AddNote(c, n); 
		c->sixth = n; 

	default:
		/* some default action... */
		break;
	}

	return 1; 
}

/* set alteration notes 
 */
static int _chord_SetAlterations(CHORD c)
{
	int i=0; 

	if (c==NULL) return -1; 

	for (i=0; i< c->chord_var_count; i++)
	{
		int var = c->chord_var[i]; 
		int varnote = var % 128; 
		NOTE n=NULL; 

		switch (varnote)
		{
			case 4: n = c->fourth; break;
			case 5: n = c->fifth; break;
			case 9: n = c->ninth; break;
			case 11: n = c->eleventh; break;
			case 13: n = c->thirteenth; break;
		}
		if ( (var&CHORD_VAR_FLAT) == CHORD_VAR_FLAT)
			note_LowerHalf(n); 
		else if ( (var&CHORD_VAR_SHARP) == CHORD_VAR_SHARP)
			note_RaiseHalf(n); 
	}
	
	return 1; 
}

int chord_GenerateNotes(CHORD c)
{
	if ( c==NULL) return -1; 

	/* fill needed notes first */
	if ( _chord_FillNotes(c) < 0 ) return -1; 

	/* set chord properties */
	switch (c->chord_def)
	{
		case CHORD_MAJOR:
				_chord_SetMajor(c); 
				break;
		case CHORD_MINOR:
				_chord_SetMinor(c); 
				break;
		case CHORD_DOMINANT:
				_chord_SetDominant(c); 
				break;
		case CHORD_HALFDIMINISHED:
				_chord_SetHalfDiminished(c); 
				break;
		case CHORD_DIMINISHED:
				_chord_SetDiminished(c); 
				break;
		case CHORD_AUGMENTED:
				_chord_SetAugmented(c); 
				break;
		case CHORD_SUSPENDED:
				_chord_SetSuspended(c); 
				break;
	}

	/* now set alteration notes */
	_chord_SetAlterations(c); 

	return 1; 	
}

		
static const char *chord2str(int chord)
{
  static const char *names[] = {
    "NONE",
    "MAJ",
    "MIN",
    "DOM",
    "HDIM",
    "DIM",
    "AUG",
    "SUS",
  };

  if (chord > 0 && chord <= CHORD_LAST)
    return names[chord];

  return names[0];
}

int chord_Print(CHORD c)
{
	if (c==NULL) return -1; 

	note_Print( &(c->basenote)); 
	printf("%s %d\n", chord2str(c->chord_def), c->chord_num); 

	{
		int i=0; 
		for (i=0; i< c->chord_var_count; i++)
		{
			int var= c->chord_var[i]; 

			printf("VAR[%d] ", i); 
			if ( (var & CHORD_VAR_FLAT)==CHORD_VAR_FLAT)
				printf("FLAT"); 
			else if ( (var & CHORD_VAR_SHARP)==CHORD_VAR_SHARP)
				printf("SHARP"); 
			else 
				printf("NONE"); 

			var = var % CHORD_VAR_SHARP; 
			printf(" %d\n", var); 
		}
	}

	/* now, print every note in the chord */
	printf("Chordtones ------->\n"); 
	{
		int i=0; 
		for (i=0; i< MAX_CHORD_NOTES; i++)
		{
			NOTE n = c->notes[i]; 
			if (n==NULL) continue; 

			note_Print(n); 
		}
	}

	return 1; 
}

int chord_ExportToString(CHORD c, char *buf, int buflen)
{
	if ( c==NULL) return -1; 

	note_ExportToString( &(c->basenote), buf, buflen ); 
	{
		char tmp[12]={0,}; 
		sprintf(tmp, "%s %d", chord2str(c->chord_def), c->chord_num); 
		strcat(buf, tmp); 
	}

	/* FIXME: add variations */

	return 1; 
}

const char *chord_GetStaticString(CHORD c)
{
	static char __chord_string_buf[24]; 
	if ( chord_ExportToString(c, __chord_string_buf, 24)< 0 )
		return NULL; 

	return (const char *)__chord_string_buf; 
}



/* calculate chord to scale mappings */
struct __chord_scale_map_elem {
	
	CHORD_TYPE chord_type; 
	CHORD_VARIATION chord_variation_arr[MAX_CHORD_VARS]; 
	SCALE_TYPE scale_type_arr[MAX_SCALES_PER_CHORD]; 
	

	/* FIXME: eg.) fifth mode of harmonic minor.. */

	/* FIXME: triad vs seventh has difference scale choices... */

	/* FIXME: a chord has different scale according to variations */

} g_chord_scale_map[] = {
	{ CHORD_MAJOR, {-1, }, { SCALE_MAJOR, -1, } }, 
	{ CHORD_MINOR, {-1, }, { SCALE_MINOR, -1, } }, 
	{ CHORD_DOMINANT, {-1, }, { SCALE_DOMINANT, -1, } }, 
	{ CHORD_AUGMENTED, {-1, }, { SCALE_WHOLETONE, -1, } }, 
	{ -1, {-1, }, { -1, } }
}; 

int chord_CalculateScales(CHORD c)
{
	int i=0; 
	NOTE root=NULL; 

	if ( c==NULL) return -1; 

	/* clear previous results */
	for (i=0; i< c->scale_count; i++)
	{
		c->scales[i] = NULL; 
	}
	c->scale_count =0; 


	root = chord_BaseNote(c); 

	for (i=0; i< g_chord_scale_map[i].chord_type > 0 ; i++)
	{
		int j=0; 

		for (j=0; g_chord_scale_map[i].scale_type_arr[j] > 0 ; j++)
		{
			SCALE s=NULL; 
			SCALE_TYPE type = g_chord_scale_map[i].scale_type_arr[j]; 

			s = scale_NewPredefined( root->abs_note, type); 
			if ( s==NULL) continue; 
		
			if ( chord_AddScale(c, s) < 0 ) continue; 
		}
		
	}

	return 1; 
}

int chord_AddScale(CHORD c, SCALE s)
{
	if (c==NULL) return -1; 
	if (s==NULL) return -1; 

	c->scales[ c->scale_count ] = s; 
	c->scale_count++; 

	return 1; 
}

int chord_GetScaleCount(CHORD c)
{
	if (c==NULL) return -1; 

	if (c->scale_count == 0 )
	{
		if ( chord_CalculateScales(c) < 0 )
		{
			return -1; 
		}
	}

	return c->scale_count; 
}

SCALE chord_GetScale(CHORD c, int i)
{
	if (c==NULL) return NULL; 
	if (i<0 ) return NULL; 

	
	if (i>= chord_GetScaleCount(c) ) return NULL; 

	return c->scales[i]; 
}

